#include "Renderer.h"

#include <fstream>
#include <random>
#include <osgText/Text>
#include <osgGA/StateSetManipulator>
#include <osgGA/TrackballManipulator>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/config/SingleWindow>

#include "Subdivide.h"

static osg::Geometry* create_node(const OpenMesh::TriMesh&);

Renderer::Renderer()
{
    try
    {
        initialize();
    }
    catch (const std::exception& e)
    {
        release();
        SPDLOG_ERROR(e.what());
    }
}

Renderer::~Renderer()
{
    release();
}

void Renderer::ReadModel(const std::string& filename)
{
    _originMesh.clear();
    if (!OpenMesh::IO::read_mesh(_originMesh, filename))
        test_open_mesh();

    Reset();
}

void Renderer::Home()
{
    _viewer->home();
}

void Renderer::Run()
{
    Home();
    _viewer->run();
}

void Renderer::Reset()
{
    _mesh.clear();
    _mesh = _originMesh;
    setGeometry(_scene, _mesh);
}

void Renderer::SetSubdivide(int mode)
{
    if (mode == 0){
        Reset();
    }
    else if (mode == 1) // loop
    {
        loop_subdivide(_mesh);
        setGeometry(_scene, _mesh);
    }
    else if (mode == 2) // square root 3
    {
        square_root_3_subdivide(_mesh);
        setGeometry(_scene, _mesh);
    }
    else if (mode == 3)
    {
        throw std::logic_error("not implement");
    }
    else
    {
        throw std::logic_error("not implement");
    }
}

void Renderer::ShowHelp(bool b)
{
    _root->setChildValue(_helpGroup, b);
}

void Renderer::initialize()
{
    if (_viewer.valid())
        return;

    _root  = new osg::Switch;
    _scene = new osg::Group;
    _root->addChild(_scene);

    _helpGroup = new osg::Group;
    _root->addChild(_helpGroup, false);
    initHelp();

    _viewer = new osgViewer::Viewer();
    _viewer->setSceneData(_root);

    _viewer->setCameraManipulator(new osgGA::OrbitManipulator);
    _viewer->addEventHandler(new osgViewer::StatsHandler);
    _viewer->addEventHandler(new osgViewer::WindowSizeHandler);
    _viewer->addEventHandler(new osgGA::StateSetManipulator(_root->getOrCreateStateSet()));
    _viewer->addEventHandler(new MyGUIEventHandler(this));

    _viewer->getCamera()->setClearColor(osg::Vec4(0.2, 0.5, 0.9, 1.0));

    _viewer->apply(new osgViewer::SingleWindow(200, 200, 800, 600));

    _viewer->realize();
}

void Renderer::release()
{
    _helpGroup = nullptr;
    _scene     = nullptr;
    _root      = nullptr;
    _viewer    = nullptr;
}

void Renderer::initHelp()
{
    std::string                 content  = R"(Press 1: Loop Subdivision
Press 2: √3 Subdivision
Press Esc: quit)";
    osg::ref_ptr<osgText::Text> textNode = new osgText::Text;
    textNode->setCharacterSize(16);
    textNode->setCharacterSizeMode(osgText::Text::SCREEN_COORDS);
    textNode->setAxisAlignment(osgText::Text::SCREEN);
    textNode->setAutoRotateToScreen(true);
    // textNode->setFont(osgText::readFontFile("C:\\Users\\daiyan\\AppData\\Local\\Microsoft\\Windows\\Fonts\\UbuntuMono-R.ttf"));
    textNode->setText(content, osgText::String::ENCODING_UTF8);
    textNode->setBackdropType(osgText::Text::OUTLINE);
    textNode->setColor(osg::Vec4(1, 1, 1, 1));
    textNode->setBackdropColor(osg::Vec4(0, 0, 0, 1));
    textNode->setPosition(osg::Vec3f(-0.95, 0.9, 0));
    osg::ref_ptr<osg::Camera> mt = new osg::Camera;
    mt->setReferenceFrame(osg::Transform::ABSOLUTE_RF_INHERIT_VIEWPOINT);
    mt->setClearMask(GL_DEPTH_BUFFER_BIT);
    mt->addChild(textNode);
    _helpGroup->addChild(mt);
}

void Renderer::setGeometry(osg::Group* group, OpenMesh::TriMesh& mesh)
{
    mesh.request_face_normals();
    mesh.request_vertex_normals();
    mesh.update_normals();

    group->removeChildren(0, group->getNumChildren());
    osg::ref_ptr<osg::Node> geom = create_node(mesh);
    group->addChild(geom);
}

void Renderer::test_open_mesh()
{
    using Point        = OpenMesh::TriMesh::Point;
    using VertexHandle = OpenMesh::TriMesh::VertexHandle;
    using FaceHandle   = OpenMesh::TriMesh::FaceHandle;

    auto& mesh = _originMesh;
    mesh.clear();

    std::vector<VertexHandle> vhandle;
    vhandle.push_back(mesh.add_vertex(Point(0, 0, 0)));
    vhandle.push_back(mesh.add_vertex(Point(1, 0, 0)));
    vhandle.push_back(mesh.add_vertex(Point(0.5, 0.867, 0)));
    vhandle.push_back(mesh.add_vertex(Point(0.5, 0.289, 1)));

    std::vector<VertexHandle> face_vhandles;

    face_vhandles.clear();
    face_vhandles.push_back(vhandle[0]);
    face_vhandles.push_back(vhandle[1]);
    face_vhandles.push_back(vhandle[3]);
    mesh.add_face(face_vhandles);

    face_vhandles.clear();
    face_vhandles.push_back(vhandle[1]);
    face_vhandles.push_back(vhandle[2]);
    face_vhandles.push_back(vhandle[3]);
    mesh.add_face(face_vhandles);

    face_vhandles.clear();
    face_vhandles.push_back(vhandle[2]);
    face_vhandles.push_back(vhandle[0]);
    face_vhandles.push_back(vhandle[3]);
    mesh.add_face(face_vhandles);

    face_vhandles.clear();
    face_vhandles.push_back(vhandle[1]);
    face_vhandles.push_back(vhandle[0]);
    face_vhandles.push_back(vhandle[2]);
    mesh.add_face(face_vhandles);
}

//-------------------------------------------------------------------


MyGUIEventHandler::MyGUIEventHandler(Renderer* r) : _renderer(r) {}

MyGUIEventHandler::~MyGUIEventHandler()
{
    _renderer = nullptr;
}

bool MyGUIEventHandler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
    static bool showHelp = false;

    if (ea.getEventType() == osgGA::GUIEventAdapter::KEYUP)
    {
        switch (ea.getKey())
        {
        case osgGA::GUIEventAdapter::KEY_0:
        case osgGA::GUIEventAdapter::KEY_1:
        case osgGA::GUIEventAdapter::KEY_2:
        case osgGA::GUIEventAdapter::KEY_3:
        case osgGA::GUIEventAdapter::KEY_4:
        case osgGA::GUIEventAdapter::KEY_5:
        case osgGA::GUIEventAdapter::KEY_6:
        case osgGA::GUIEventAdapter::KEY_7:
        case osgGA::GUIEventAdapter::KEY_8:
        case osgGA::GUIEventAdapter::KEY_9:
        {
            try
            {
                _renderer->SetSubdivide(ea.getKey() - osgGA::GUIEventAdapter::KEY_0);
            }
            catch (const std::exception& e)
            {
                SPDLOG_WARN(e.what());
            }
        }
        break;
        case osgGA::GUIEventAdapter::KEY_H: _renderer->ShowHelp(showHelp = !showHelp); break;
        default: break;
        }
    }

    return false;
}


//-------------------------------------------------------------------

osg::Geometry* create_node(const OpenMesh::TriMesh& mesh)
{
    std::random_device                    r;
    std::default_random_engine            g(r());
    std::uniform_real_distribution<float> urd(0, 1);

    osg::ref_ptr<osg::Vec3Array> verts = new osg::Vec3Array;
    osg::ref_ptr<osg::Vec3Array> norms = new osg::Vec3Array;
    verts->reserve(mesh.n_vertices());
    norms->reserve(mesh.n_vertices());
    for (auto vh : mesh.vertices())
    {
        const auto& x = mesh.point(vh);
        const auto& n = mesh.normal(vh);
        verts->push_back(osg::Vec3(x[0], x[1], x[2]));
        norms->push_back(osg::Vec3(n[0], n[1], n[2]));
    }

    osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
    colors->push_back(osg::Vec4(urd(g), urd(g), urd(g), 1.0f));

    osg::ref_ptr<osg::DrawElementsUInt> elem = new osg::DrawElementsUInt();
    elem->setMode(GL_TRIANGLES);
    for (auto fh : mesh.faces())
    {
        for (auto vh : fh.vertices())
            elem->push_back(vh.idx());
    }

    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
    geom->setUseVertexBufferObjects(true);
    geom->setVertexArray(verts);
    geom->setNormalArray(norms, osg::Array::BIND_PER_VERTEX);
    geom->setColorArray(colors, osg::Array::BIND_OVERALL);
    geom->addPrimitiveSet(elem);

    return geom.release();
}
